package main

import (
	"errors"
	"fmt"
	"sync"
	"time"
)

var table *Cache

func init() {
	table = new(Cache)
	table.Loc = &sync.RWMutex{}
	table.Data = make(map[string]*CacheItem)
}

var ErrNil = errors.New("key not exist")

type CacheItem struct {
	Value interface{}
	Exp   time.Duration
}

type Cache struct {
	Data map[string]*CacheItem
	Loc  *sync.RWMutex
}

func (c *Cache) Set(key string, value interface{}, exp time.Duration) {
	c.Loc.Lock()
	defer c.Loc.Unlock()
	c.Data[key] = &CacheItem{
		Value: value,
		Exp:   exp,
	}
	if exp != time.Duration(-1) {
		go time.AfterFunc(exp, func() {
			delete(c.Data, key)
		})
	}
}

func (c *Cache) Get(key string) (value interface{}, err error) {
	c.Loc.RLock()
	defer c.Loc.RUnlock()
	if v, exist := c.Data[key]; !exist {
		return v, ErrNil
	} else {
		return v, nil
	}
}

func (c *Cache) Del(key string) {
	c.Loc.Lock()
	defer c.Loc.Unlock()
	delete(c.Data, key)
}

func (c *Cache) Incr(key string, step int) error {
	actionLock := sync.RWMutex{}
	actionLock.Lock()
	defer actionLock.Unlock()
	cacheFace, err := c.Get(key)
	if err != nil {
		return err
	}
	cache, assert := cacheFace.(int)
	if !assert {
		return errors.New("not int")
	}
	cache += step
	item, _ := c.Data[key]
	c.Set(key, cache, item.Exp)
	return nil
}

var totalNotSafety int
var totalSafety int
var cacheKey string

func main() {
	cacheKey = "total"
	totalNotSafety = 10000
	totalSafety = totalNotSafety
	table.Set(cacheKey, totalSafety, -1)

	for i := 0; i < totalNotSafety; i++ {
		go func(t int) {
			totalNotSafety = t - 1
		}(totalNotSafety)
	}

	for i := 0; i < totalSafety; i++ {
		go func() {
			_ = table.Incr(cacheKey, -1)
		}()
	}
	time.Sleep(10 * time.Second)

	totalSafety, _ := table.Get(cacheKey)
	totalSafetyInt, _ := totalSafety.(int)
	fmt.Println(totalNotSafety, totalSafetyInt)
}
